/*
 * Copyright 2015-2016 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * You may not use this file except in compliance with the terms and conditions set forth
 * in the accompanying LICENSE.TXT file.
 *
 * THESE MATERIALS ARE PROVIDED ON AN "AS IS" BASIS.  AMAZON SPECIFICALLY DISCLAIMS, WITH
 * RESPECT TO THESE MATERIALS, ALL WARRANTIES, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
 */

package com.amazon.alexa.voicechrome.internal.audio;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.util.Log;

public class AudioUtil {
    private static final int RECORDER_SAMPLERATE = 44100;
    private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_MONO;
    private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;

    private static final float HIGH_THRESHOLD = 1000.0f;
    private static final float LOW_THRESHOLD = 200.0f;

    private AudioRecord recorder = null;
    private AsyncTask<Void, short[], Void> recordingThread = null;

    private boolean isRecording = false;

    private int bufferSize;

    private OnRecordListener onRecordListener;
    private boolean loggingEnabled;

    public interface OnRecordListener {
        void onRelativeVolumeChanged(float volume);
    }

    public void setOnRecordListener(OnRecordListener listener) {
        onRecordListener = listener;
    }

    /**
     * Start recording and posting amplitude updates
     */
    public void start() {
        if (loggingEnabled) {
            Log.d(AudioUtil.class.getSimpleName(), "Starting recording");
        }
        if (recorder != null && recorder.getState() == AudioRecord.STATE_INITIALIZED) {
            return;
        }

        bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE, RECORDER_CHANNELS,
                RECORDER_AUDIO_ENCODING);

        recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
                RECORDER_SAMPLERATE, RECORDER_CHANNELS,
                RECORDER_AUDIO_ENCODING, bufferSize);

        if (recorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
            return;
        }

        recorder.startRecording();
        isRecording = true;
        recordingThread = new AsyncTask<Void, short[], Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                while (isRecording) {
                    try {
                        short[] buffer = new short[bufferSize];

                        if (recorder != null) {
                            // Sense the voice...
                            recorder.read(buffer, 0, bufferSize);

                            publishProgress(buffer);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                return null;
            }

            @Override
            protected void onProgressUpdate(short[]... values) {
                super.onProgressUpdate(values);

                if (!isRecording) {
                    return;
                }

                double rms = getRMS(values[0]);
                rms -= LOW_THRESHOLD;
                rms = Math.max(rms, 0f);
                if (loggingEnabled) {
                    Log.d(AudioUtil.class.getSimpleName(), "RMS: " + rms);
                }

                if (onRecordListener != null) onRecordListener.onRelativeVolumeChanged((float) Math.min(rms / HIGH_THRESHOLD, 1.0f));
            }
        };

        recordingThread.execute();
    }

    /**
     * Stop recording and reset threads and states if currently recording
     */
    public void stop() {
        if (isRecording || recordingThread != null || recorder != null) {
            if (loggingEnabled) {
                Log.d(AudioUtil.class.getSimpleName(), "Stop recording");
            }
            isRecording = false;
            recordingThread = null;

            if (recorder != null) {
                if (recorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                    recorder.stop();
                }
                recorder.release();
                recorder = null;
            }
        }
    }

    private double getRMS(short[] audioData) {
        double rms = 0;
        for (int i = 0; i < audioData.length; i++) {
            rms += audioData[i] * audioData[i];
        }

        return Math.sqrt(rms/audioData.length);
    }

    public void setLoggingEnabled(boolean loggingEnabled) {
        this.loggingEnabled = loggingEnabled;
    }
}
